/*
* "$Id: mxml-node.c 436 2011-01-22 01:02:05Z mike $"
*
* Node support code for Mini-XML, a small XML-like file parsing library.
*
* Copyright 2003-2011 by Michael R Sweet.
*
* These coded instructions, statements, and computer programs are the
* property of Michael R Sweet and are protected by Federal copyright
* law.  Distribution and use rights are outlined in the file "COPYING"
* which should have been included with this file.  If this file is
* missing or damaged, see the license at:
*
*     http://www.minixml.org/
*
* Contents:
*
*   mxmlAdd()         - Add a node to a tree.
*   mxmlDelete()      - Delete a node and all of its children.
*   mxmlGetRefCount() - Get the current reference (use) count for a node.
*   mxmlNewCDATA()    - Create a new CDATA node.
*   mxmlNewCustom()   - Create a new custom data node.
*   mxmlNewElement()  - Create a new element node.
*   mxmlNewInteger()  - Create a new integer node.
*   mxmlNewOpaque()   - Create a new opaque string.
*   mxmlNewReal()     - Create a new real number node.
*   mxmlNewText()     - Create a new text fragment node.
*   mxmlNewTextf()    - Create a new formatted text fragment node.
*   mxmlRemove()      - Remove a node from its parent.
*   mxmlNewXML()      - Create a new XML document tree.
*   mxmlRelease()     - Release a node.
*   mxmlRetain()      - Retain a node.
*   mxml_new()        - Create a new node.
*/

/*
* Include necessary headers...
*/

#include "mxml-config.h"
#include "mxml.h"


/*
* Local functions...
*/

static mxml_node_t	*mxml_new(mxml_node_t *parent, mxml_type_t type);


/*
* 'mxmlAdd()' - Add a node to a tree.
*
* Adds the specified node to the parent. If the child argument is not
* NULL, puts the new node before or after the specified child depending
* on the value of the where argument. If the child argument is NULL,
* puts the new node at the beginning of the child list (MXML_ADD_BEFORE)
* or at the end of the child list (MXML_ADD_AFTER). The constant
* MXML_ADD_TO_PARENT can be used to specify a NULL child pointer.
*/

void
	mxmlAdd(mxml_node_t *parent,		/* I - Parent node */
	int         where,		/* I - Where to add, MXML_ADD_BEFORE or MXML_ADD_AFTER */
	mxml_node_t *child,		/* I - Child node for where or MXML_ADD_TO_PARENT */
	mxml_node_t *node)		/* I - Node to add */
{
#ifdef DEBUG
	fprintf(stderr, "mxmlAdd(parent=%p, where=%d, child=%p, node=%p)\n", parent,
	where, child, node);
#endif /* DEBUG */

	/*
	* Range check input...
	*/

	if (!parent || !node)
		return;

#if DEBUG > 1
	fprintf(stderr, "    BEFORE: node->parent=%p\n", node->parent);
	if (parent)
	{
		fprintf(stderr, "    BEFORE: parent->child=%p\n", parent->child);
		fprintf(stderr, "    BEFORE: parent->last_child=%p\n", parent->last_child);
		fprintf(stderr, "    BEFORE: parent->prev=%p\n", parent->prev);
		fprintf(stderr, "    BEFORE: parent->next=%p\n", parent->next);
	}
#endif /* DEBUG > 1 */

	/*
	* Remove the node from any existing parent...
	*/

	if (node->parent)
		mxmlRemove(node);

	/*
	* Reset pointers...
	*/

	node->parent = parent;

	switch (where)
	{
	case MXML_ADD_BEFORE :
		if (!child || child == parent->child || child->parent != parent)
		{
			/*
			* Insert as first node under parent...
			*/

			node->next = parent->child;

			if (parent->child)
				parent->child->prev = node;
			else
				parent->last_child = node;

			parent->child = node;
		}
		else
		{
			/*
			* Insert node before this child...
			*/

			node->next = child;
			node->prev = child->prev;

			if (child->prev)
				child->prev->next = node;
			else
				parent->child = node;

			child->prev = node;
		}
		break;

	case MXML_ADD_AFTER :
		if (!child || child == parent->last_child || child->parent != parent)
		{
			/*
			* Insert as last node under parent...
			*/

			node->parent = parent;
			node->prev   = parent->last_child;

			if (parent->last_child)
				parent->last_child->next = node;
			else
				parent->child = node;

			parent->last_child = node;
		}
		else
		{
			/*
			* Insert node after this child...
			*/

			node->prev = child;
			node->next = child->next;

			if (child->next)
				child->next->prev = node;
			else
				parent->last_child = node;

			child->next = node;
		}
		break;
	}

#if DEBUG > 1
	fprintf(stderr, "    AFTER: node->parent=%p\n", node->parent);
	if (parent)
	{
		fprintf(stderr, "    AFTER: parent->child=%p\n", parent->child);
		fprintf(stderr, "    AFTER: parent->last_child=%p\n", parent->last_child);
		fprintf(stderr, "    AFTER: parent->prev=%p\n", parent->prev);
		fprintf(stderr, "    AFTER: parent->next=%p\n", parent->next);
	}
#endif /* DEBUG > 1 */
}


/*
* 'mxmlDelete()' - Delete a node and all of its children.
*
* If the specified node has a parent, this function first removes the
* node from its parent using the mxmlRemove() function.
*/

void
	mxmlDelete(mxml_node_t *node)		/* I - Node to delete */
{
	int	i;				/* Looping var */


#ifdef DEBUG
	fprintf(stderr, "mxmlDelete(node=%p)\n", node);
#endif /* DEBUG */

	/*
	* Range check input...
	*/

	if (!node)
		return;

	/*
	* Remove the node from its parent, if any...
	*/

	mxmlRemove(node);

	/*
	* Delete children...
	*/

	while (node->child)
		mxmlDelete(node->child);

	/*
	* Now delete any node data...
	*/

	switch (node->type)
	{
	case MXML_ELEMENT :
		if (node->value.element.name)
			free(node->value.element.name);

		if (node->value.element.num_attrs)
		{
			for (i = 0; i < node->value.element.num_attrs; i ++)
			{
				if (node->value.element.attrs[i].name)
					free(node->value.element.attrs[i].name);
				if (node->value.element.attrs[i].value)
					free(node->value.element.attrs[i].value);
			}

			free(node->value.element.attrs);
		}
		break;
	case MXML_INTEGER :
		/* Nothing to do */
		break;
	case MXML_OPAQUE :
		if (node->value.opaque)
			free(node->value.opaque);
		break;
	case MXML_REAL :
		/* Nothing to do */
		break;
	case MXML_TEXT :
		if (node->value.text.string)
			free(node->value.text.string);
		break;
	case MXML_CUSTOM :
		if (node->value.custom.data &&
			node->value.custom.destroy)
			(*(node->value.custom.destroy))(node->value.custom.data);
		break;
	default :
		break;
	}

	/*
	* Free this node...
	*/

	free(node);
}


/*
* 'mxmlGetRefCount()' - Get the current reference (use) count for a node.
*
* The initial reference count of new nodes is 1. Use the @link mxmlRetain@
* and @link mxmlRelease@ functions to increment and decrement a node's
* reference count.
*
* @since Mini-XML 2.7@.
*/

int					/* O - Reference count */
	mxmlGetRefCount(mxml_node_t *node)	/* I - Node */
{
	/*
	* Range check input...
	*/

	if (!node)
		return (0);

	/*
	* Return the reference count...
	*/

	return (node->ref_count);
}


/*
* 'mxmlNewCDATA()' - Create a new CDATA node.
*
* The new CDATA node is added to the end of the specified parent's child
* list. The constant MXML_NO_PARENT can be used to specify that the new
* CDATA node has no parent. The data string must be nul-terminated and
* is copied into the new node. CDATA nodes use the MXML_ELEMENT type.
*
* @since Mini-XML 2.3@
*/

mxml_node_t *				/* O - New node */
	mxmlNewCDATA(mxml_node_t *parent,	/* I - Parent node or MXML_NO_PARENT */
	const char  *data)		/* I - Data string */
{
	mxml_node_t	*node;			/* New node */


#ifdef DEBUG
	fprintf(stderr, "mxmlNewCDATA(parent=%p, data=\"%s\")\n",
		parent, data ? data : "(null)");
#endif /* DEBUG */

	/*
	* Range check input...
	*/

	if (!data)
		return (NULL);

	/*
	* Create the node and set the name value...
	*/

	if ((node = mxml_new(parent, MXML_ELEMENT)) != NULL)
		node->value.element.name = _mxml_strdupf("![CDATA[%s]]", data);

	return (node);
}


/*
* 'mxmlNewCustom()' - Create a new custom data node.
*
* The new custom node is added to the end of the specified parent's child
* list. The constant MXML_NO_PARENT can be used to specify that the new
* element node has no parent. NULL can be passed when the data in the
* node is not dynamically allocated or is separately managed.
*
* @since Mini-XML 2.1@
*/

mxml_node_t *				/* O - New node */
	mxmlNewCustom(
	mxml_node_t              *parent,	/* I - Parent node or MXML_NO_PARENT */
	void                     *data,	/* I - Pointer to data */
	mxml_custom_destroy_cb_t destroy)	/* I - Function to destroy data */
{
	mxml_node_t	*node;			/* New node */


#ifdef DEBUG
	fprintf(stderr, "mxmlNewCustom(parent=%p, data=%p, destroy=%p)\n", parent,
		data, destroy);
#endif /* DEBUG */

	/*
	* Create the node and set the value...
	*/

	if ((node = mxml_new(parent, MXML_CUSTOM)) != NULL)
	{
		node->value.custom.data    = data;
		node->value.custom.destroy = destroy;
	}

	return (node);
}


/*
* 'mxmlNewElement()' - Create a new element node.
*
* The new element node is added to the end of the specified parent's child
* list. The constant MXML_NO_PARENT can be used to specify that the new
* element node has no parent.
*/

mxml_node_t *				/* O - New node */
	mxmlNewElement(mxml_node_t *parent,	/* I - Parent node or MXML_NO_PARENT */
	const char  *name)	/* I - Name of element */
{
	mxml_node_t	*node;			/* New node */


#ifdef DEBUG
	fprintf(stderr, "mxmlNewElement(parent=%p, name=\"%s\")\n", parent,
		name ? name : "(null)");
#endif /* DEBUG */

	/*
	* Range check input...
	*/

	if (!name)
		return (NULL);

	/*
	* Create the node and set the element name...
	*/

	if ((node = mxml_new(parent, MXML_ELEMENT)) != NULL)
		node->value.element.name = strdup(name);

	return (node);
}


/*
* 'mxmlNewInteger()' - Create a new integer node.
*
* The new integer node is added to the end of the specified parent's child
* list. The constant MXML_NO_PARENT can be used to specify that the new
* integer node has no parent.
*/

mxml_node_t *				/* O - New node */
	mxmlNewInteger(mxml_node_t *parent,	/* I - Parent node or MXML_NO_PARENT */
	int         integer)	/* I - Integer value */
{
	mxml_node_t	*node;			/* New node */


#ifdef DEBUG
	fprintf(stderr, "mxmlNewInteger(parent=%p, integer=%d)\n", parent, integer);
#endif /* DEBUG */

	/*
	* Create the node and set the element name...
	*/

	if ((node = mxml_new(parent, MXML_INTEGER)) != NULL)
		node->value.integer = integer;

	return (node);
}


/*
* 'mxmlNewOpaque()' - Create a new opaque string.
*
* The new opaque node is added to the end of the specified parent's child
* list. The constant MXML_NO_PARENT can be used to specify that the new
* opaque node has no parent. The opaque string must be nul-terminated and
* is copied into the new node.
*/

mxml_node_t *				/* O - New node */
	mxmlNewOpaque(mxml_node_t *parent,	/* I - Parent node or MXML_NO_PARENT */
	const char  *opaque)	/* I - Opaque string */
{
	mxml_node_t	*node;			/* New node */


#ifdef DEBUG
	fprintf(stderr, "mxmlNewOpaque(parent=%p, opaque=\"%s\")\n", parent,
		opaque ? opaque : "(null)");
#endif /* DEBUG */

	/*
	* Range check input...
	*/

	if (!opaque)
		return (NULL);

	/*
	* Create the node and set the element name...
	*/

	if ((node = mxml_new(parent, MXML_OPAQUE)) != NULL)
		node->value.opaque = strdup(opaque);

	return (node);
}


/*
* 'mxmlNewReal()' - Create a new real number node.
*
* The new real number node is added to the end of the specified parent's
* child list. The constant MXML_NO_PARENT can be used to specify that
* the new real number node has no parent.
*/

mxml_node_t *				/* O - New node */
	mxmlNewReal(mxml_node_t *parent,	/* I - Parent node or MXML_NO_PARENT */
	double      real)		/* I - Real number value */
{
	mxml_node_t	*node;			/* New node */


#ifdef DEBUG
	fprintf(stderr, "mxmlNewReal(parent=%p, real=%g)\n", parent, real);
#endif /* DEBUG */

	/*
	* Create the node and set the element name...
	*/

	if ((node = mxml_new(parent, MXML_REAL)) != NULL)
		node->value.real = real;

	return (node);
}


/*
* 'mxmlNewText()' - Create a new text fragment node.
*
* The new text node is added to the end of the specified parent's child
* list. The constant MXML_NO_PARENT can be used to specify that the new
* text node has no parent. The whitespace parameter is used to specify
* whether leading whitespace is present before the node. The text
* string must be nul-terminated and is copied into the new node.  
*/

mxml_node_t *				/* O - New node */
	mxmlNewText(mxml_node_t *parent,	/* I - Parent node or MXML_NO_PARENT */
	int         whitespace,	/* I - 1 = leading whitespace, 0 = no whitespace */
	const char  *string)	/* I - String */
{
	mxml_node_t	*node;			/* New node */


#ifdef DEBUG
	fprintf(stderr, "mxmlNewText(parent=%p, whitespace=%d, string=\"%s\")\n",
		parent, whitespace, string ? string : "(null)");
#endif /* DEBUG */

	/*
	* Range check input...
	*/

	if (!string)
		return (NULL);

	/*
	* Create the node and set the text value...
	*/

	if ((node = mxml_new(parent, MXML_TEXT)) != NULL)
	{
		node->value.text.whitespace = whitespace;
		node->value.text.string     = strdup(string);
	}

	return (node);
}


/*
* 'mxmlNewTextf()' - Create a new formatted text fragment node.
*
* The new text node is added to the end of the specified parent's child
* list. The constant MXML_NO_PARENT can be used to specify that the new
* text node has no parent. The whitespace parameter is used to specify
* whether leading whitespace is present before the node. The format
* string must be nul-terminated and is formatted into the new node.  
*/

mxml_node_t *				/* O - New node */
	mxmlNewTextf(mxml_node_t *parent,	/* I - Parent node or MXML_NO_PARENT */
	int         whitespace,	/* I - 1 = leading whitespace, 0 = no whitespace */
	const char  *format,	/* I - Printf-style frmat string */
	...)			/* I - Additional args as needed */
{
	mxml_node_t	*node;			/* New node */
	va_list	ap;			/* Pointer to arguments */


#ifdef DEBUG
	fprintf(stderr, "mxmlNewTextf(parent=%p, whitespace=%d, format=\"%s\", ...)\n",
		parent, whitespace, format ? format : "(null)");
#endif /* DEBUG */

	/*
	* Range check input...
	*/

	if (!format)
		return (NULL);

	/*
	* Create the node and set the text value...
	*/

	if ((node = mxml_new(parent, MXML_TEXT)) != NULL)
	{
		va_start(ap, format);

		node->value.text.whitespace = whitespace;
		node->value.text.string     = _mxml_vstrdupf(format, ap);

		va_end(ap);
	}

	return (node);
}


/*
* 'mxmlRemove()' - Remove a node from its parent.
*
* Does not free memory used by the node - use mxmlDelete() for that.
* This function does nothing if the node has no parent.
*/

void
	mxmlRemove(mxml_node_t *node)		/* I - Node to remove */
{
#ifdef DEBUG
	fprintf(stderr, "mxmlRemove(node=%p)\n", node);
#endif /* DEBUG */

	/*
	* Range check input...
	*/

	if (!node || !node->parent)
		return;

	/*
	* Remove from parent...
	*/

#if DEBUG > 1
	fprintf(stderr, "    BEFORE: node->parent=%p\n", node->parent);
	if (node->parent)
	{
		fprintf(stderr, "    BEFORE: node->parent->child=%p\n", node->parent->child);
		fprintf(stderr, "    BEFORE: node->parent->last_child=%p\n", node->parent->last_child);
	}
	fprintf(stderr, "    BEFORE: node->child=%p\n", node->child);
	fprintf(stderr, "    BEFORE: node->last_child=%p\n", node->last_child);
	fprintf(stderr, "    BEFORE: node->prev=%p\n", node->prev);
	fprintf(stderr, "    BEFORE: node->next=%p\n", node->next);
#endif /* DEBUG > 1 */

	if (node->prev)
		node->prev->next = node->next;
	else
		node->parent->child = node->next;

	if (node->next)
		node->next->prev = node->prev;
	else
		node->parent->last_child = node->prev;

	node->parent = NULL;
	node->prev   = NULL;
	node->next   = NULL;

#if DEBUG > 1
	fprintf(stderr, "    AFTER: node->parent=%p\n", node->parent);
	if (node->parent)
	{
		fprintf(stderr, "    AFTER: node->parent->child=%p\n", node->parent->child);
		fprintf(stderr, "    AFTER: node->parent->last_child=%p\n", node->parent->last_child);
	}
	fprintf(stderr, "    AFTER: node->child=%p\n", node->child);
	fprintf(stderr, "    AFTER: node->last_child=%p\n", node->last_child);
	fprintf(stderr, "    AFTER: node->prev=%p\n", node->prev);
	fprintf(stderr, "    AFTER: node->next=%p\n", node->next);
#endif /* DEBUG > 1 */
}


/*
* 'mxmlNewXML()' - Create a new XML document tree.
*
* The "version" argument specifies the version number to put in the
* ?xml element node. If NULL, version 1.0 is assumed.
*
* @since Mini-XML 2.3@
*/

mxml_node_t *				/* O - New ?xml node */
	mxmlNewXML(const char *version)		/* I - Version number to use */
{
	char	element[1024];			/* Element text */


	snprintf(element, sizeof(element), "?xml version=\"%s\" encoding=\"utf-8\"?",
		version ? version : "1.0");

	return (mxmlNewElement(NULL, element));
}


/*
* 'mxmlRelease()' - Release a node.
*
* When the reference count reaches zero, the node (and any children)
* is deleted via mxmlDelete().
*
* @since Mini-XML 2.3@
*/

int					/* O - New reference count */
	mxmlRelease(mxml_node_t *node)		/* I - Node */
{
	if (node)
	{
		if ((-- node->ref_count) <= 0)
		{
			mxmlDelete(node);
			return (0);
		}
		else
			return (node->ref_count);
	}
	else
		return (-1);
}


/*
* 'mxmlRetain()' - Retain a node.
*
* @since Mini-XML 2.3@
*/

int					/* O - New reference count */
	mxmlRetain(mxml_node_t *node)		/* I - Node */
{
	if (node)
		return (++ node->ref_count);
	else
		return (-1);
}


/*
* 'mxml_new()' - Create a new node.
*/

static mxml_node_t *			/* O - New node */
	mxml_new(mxml_node_t *parent,		/* I - Parent node */
	mxml_type_t type)		/* I - Node type */
{
	mxml_node_t	*node;			/* New node */


#if DEBUG > 1
	fprintf(stderr, "mxml_new(parent=%p, type=%d)\n", parent, type);
#endif /* DEBUG > 1 */

	/*
	* Allocate memory for the node...
	*/

	if ((node = calloc(1, sizeof(mxml_node_t))) == NULL)
	{
#if DEBUG > 1
		fputs("    returning NULL\n", stderr);
#endif /* DEBUG > 1 */

		return (NULL);
	}

#if DEBUG > 1
	fprintf(stderr, "    returning %p\n", node);
#endif /* DEBUG > 1 */

	/*
	* Set the node type...
	*/

	node->type      = type;
	node->ref_count = 1;

	/*
	* Add to the parent if present...
	*/

	if (parent)
		mxmlAdd(parent, MXML_ADD_AFTER, MXML_ADD_TO_PARENT, node);

	/*
	* Return the new node...
	*/

	return (node);
}


/*
* End of "$Id: mxml-node.c 436 2011-01-22 01:02:05Z mike $".
*/
